// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

// IO interface implementations for OS files

#pragma once

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include <nebula/io/concurrency.h>
#include <nebula/io/interfaces.h>
#include <turbo/base/macros.h>

namespace nebula {

    class Buffer;

    class MemoryPool;


    namespace io {

        /// \brief An operating system file open in write-only mode.
        class TURBO_EXPORT FileOutputStream : public OutputStream {
        public:
            ~FileOutputStream() override;

            /// \brief open a local file for writing, truncating any existing file
            /// \param[in] path with UTF8 encoding
            /// \param[in] append append to existing file, otherwise truncate to 0 bytes
            /// \return an open FileOutputStream
            ///
            /// When opening a new file, any existing file with the indicated path is
            /// truncated to 0 bytes, deleting any existing data
            static turbo::Result<std::shared_ptr<FileOutputStream>> open(const std::string &path,
                                                                  bool append = false);

            /// \brief open a file descriptor for writing.  The underlying file isn't
            /// truncated.
            /// \param[in] fd file descriptor
            /// \return an open FileOutputStream
            ///
            /// The file descriptor becomes owned by the OutputStream, and will be closed
            /// on close() or destruction.
            static turbo::Result<std::shared_ptr<FileOutputStream>> open(int fd);

            // OutputStream interface
            turbo::Status close() override;

            bool closed() const override;

            turbo::Result<int64_t> tell() const override;

            // write bytes to the stream. Thread-safe
            turbo::Status write(const void *data, int64_t nbytes) override;
            /// \cond FALSE
            using Writable::write;
            /// \endcond

            int file_descriptor() const;

        private:
            FileOutputStream();

            class TURBO_NO_EXPORT FileOutputStreamImpl;

            std::unique_ptr<FileOutputStreamImpl> impl_;
        };

        /// \brief An operating system file open in read-only mode.
        ///
        /// Reads through this implementation are unbuffered.  If many small reads
        /// need to be issued, it is recommended to use a buffering layer for good
        /// performance.
        class TURBO_EXPORT ReadableFile
                : public internal::RandomAccessFileConcurrencyWrapper<ReadableFile> {
        public:
            ~ReadableFile() override;

            /// \brief open a local file for reading
            /// \param[in] path with UTF8 encoding
            /// \param[in] pool a MemoryPool for memory allocations
            /// \return ReadableFile instance
            static turbo::Result<std::shared_ptr<ReadableFile>> open(
                    const std::string &path, MemoryPool *pool = default_memory_pool());

            /// \brief open a local file for reading
            /// \param[in] fd file descriptor
            /// \param[in] pool a MemoryPool for memory allocations
            /// \return ReadableFile instance
            ///
            /// The file descriptor becomes owned by the ReadableFile, and will be closed
            /// on close() or destruction.
            static turbo::Result<std::shared_ptr<ReadableFile>> open(
                    int fd, MemoryPool *pool = default_memory_pool());

            bool closed() const override;

            int file_descriptor() const;

            turbo::Status will_need(const std::vector<ReadRange> &ranges) override;

        private:
            friend RandomAccessFileConcurrencyWrapper<ReadableFile>;

            explicit ReadableFile(MemoryPool *pool);

            turbo::Status DoClose();

            turbo::Result<int64_t> DoTell() const;

            turbo::Result<int64_t> DoRead(int64_t nbytes, void *buffer);

            turbo::Result<std::shared_ptr<Buffer>> DoRead(int64_t nbytes);

            /// \brief Thread-safe implementation of read_at
            turbo::Result<int64_t> DoReadAt(int64_t position, int64_t nbytes, void *out);

            /// \brief Thread-safe implementation of read_at
            turbo::Result<std::shared_ptr<Buffer>> DoReadAt(int64_t position, int64_t nbytes);

            turbo::Result<int64_t> DoGetSize();

            turbo::Status DoSeek(int64_t position);

            class TURBO_NO_EXPORT ReadableFileImpl;

            std::unique_ptr<ReadableFileImpl> impl_;
        };

        /// \brief A file interface that uses memory-mapped files for memory interactions
        ///
        /// This implementation supports zero-copy reads. The same class is used
        /// for both reading and writing.
        ///
        /// If opening a file in a writable mode, it is not truncated first as with
        /// FileOutputStream.
        class TURBO_EXPORT MemoryMappedFile : public ReadWriteFileInterface {
        public:
            ~MemoryMappedFile() override;

            /// create new file with indicated size, return in read/write mode
            static turbo::Result<std::shared_ptr<MemoryMappedFile>> create(const std::string &path,
                                                                    int64_t size);

            // mmap() with whole file
            static turbo::Result<std::shared_ptr<MemoryMappedFile>> open(const std::string &path,
                                                                  FileMode::type mode);

            // mmap() with a region of file, the offset must be a multiple of the page size
            static turbo::Result<std::shared_ptr<MemoryMappedFile>> open(const std::string &path,
                                                                  FileMode::type mode,
                                                                  const int64_t offset,
                                                                  const int64_t length);

            turbo::Status close() override;

            bool closed() const override;

            turbo::Result<int64_t> tell() const override;

            turbo::Status Seek(int64_t position) override;

            // Required by RandomAccessFile, copies memory into out. Not thread-safe
            turbo::Result<int64_t> read(int64_t nbytes, void *out) override;

            // Zero copy read, moves position pointer. Not thread-safe
            turbo::Result<std::shared_ptr<Buffer>> read(int64_t nbytes) override;

            // Zero-copy read, leaves position unchanged. Acquires a reader lock
            // for the duration of slice creation (typically very short). Is thread-safe.
            turbo::Result<std::shared_ptr<Buffer>> read_at(int64_t position, int64_t nbytes) override;

            // Raw copy of the memory at specified position. Thread-safe, but
            // locks out other readers for the duration of memcpy. Prefer the
            // zero copy method
            turbo::Result<int64_t> read_at(int64_t position, int64_t nbytes, void *out) override;

            // Synchronous read_async override
            Future<std::shared_ptr<Buffer>> read_async(const IOContext &, int64_t position,
                                                      int64_t nbytes) override;

            turbo::Status will_need(const std::vector<ReadRange> &ranges) override;

            bool supports_zero_copy() const override;

            /// write data at the current position in the file. Thread-safe
            turbo::Status write(const void *data, int64_t nbytes) override;
            /// \cond FALSE
            using Writable::write;
            /// \endcond

            /// Set the size of the map to new_size.
            turbo::Status resize(int64_t new_size);

            /// write data at a particular position in the file. Thread-safe
            turbo::Status write_at(int64_t position, const void *data, int64_t nbytes) override;

            turbo::Result<int64_t> get_size() override;

            int file_descriptor() const;

        private:
            MemoryMappedFile();

            turbo::Status WriteInternal(const void *data, int64_t nbytes);

            class TURBO_NO_EXPORT MemoryMap;

            std::shared_ptr<MemoryMap> memory_map_;
        };

    }  // namespace io
}  // namespace nebula
